/*
 * $Id: DatabaseIO.java 23 2009-03-31 23:37:36Z aaron.day $
 */
package org.crazydays.gameplan.data;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.crazydays.gameplan.map.GameMap;
import org.crazydays.gameplan.map.IGameMap;

/**
 * DatabaseIO
 */
public class DatabaseIO
{
	/** log */
	protected static Log LOG = LogFactory.getLog(DatabaseIO.class);

	/**
	 * Load database from file.
	 * 
	 * @param file File
	 * @return Database
	 * @throws IOException
	 */
	public static Database load(File file)
	        throws IOException
	{
		if (file == null) {
			throw new IllegalArgumentException("file == null");
		}

		InputStream inputStream = new FileInputStream(file);
		try {
			return load(inputStream);
		} finally {
			if (inputStream == null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					LOG.warn("Unable to close stream", e);
				}
			}
		}
	}

	/**
	 * Load database from input stream.
	 * 
	 * @param inputStream InputStream
	 * @return Database
	 * @throws IOException
	 */
	public static Database load(InputStream inputStream)
	        throws IOException
	{
		if (inputStream == null) {
			throw new IllegalArgumentException(
			        "inputStream == null");
		}

		return load(new ZipInputStream(inputStream));
	}

	/**
	 * Load database from zip input stream.
	 * 
	 * @param zipInputStream ZipInputStream
	 * @return Database
	 * @throws IOException
	 */
	public static Database load(ZipInputStream zipInputStream)
	        throws IOException
	{
		if (zipInputStream == null) {
			throw new IllegalArgumentException(
			        "zipInputStream == null");
		}

		// game maps
		Map<String, IGameMap> gameMaps =
		        new HashMap<String, IGameMap>();

		// TODO: add additional document types

		ZipEntry zipEntry = null;
		while ((zipEntry = zipInputStream.getNextEntry()) != null) {
			String type = getType(zipEntry);

			if (GameMapIO.MAP_DIRECTORY.equals(type)) {
				processGameMapZipEntry(zipInputStream,
				        zipEntry, gameMaps);
			} else {
				LOG.warn("Ignoring unknown type: " + type);
			}
		}

		// TODO: validate the game maps

		// setup database
		Database database = new Database();
		LOG.debug("Creating database");

		// add all game maps
		for (IGameMap gameMap : gameMaps.values()) {
			database.addGameMap(gameMap);
			LOG.debug("Adding map: " + gameMap.getName());
		}

		return database;
	}

	/**
	 * Get object type.
	 * 
	 * @param zipEntry ZipEntry
	 * @return Type
	 */
	protected static String getType(ZipEntry zipEntry)
	{
		if (zipEntry == null) {
			throw new IllegalArgumentException("zipEntry == null");
		}

		return zipEntry.getName().replaceAll("(.*?)[/\\\\].*?\\..*$",
		        "$1");
	}

	/**
	 * Process game map zip entry.
	 * 
	 * @param zipInputStream ZipInputStream
	 * @param zipEntry ZipEntry
	 * @param gameMaps GameMaps
	 * @throws IOException
	 */
	protected static void processGameMapZipEntry(
	        ZipInputStream zipInputStream, ZipEntry zipEntry,
	        Map<String, IGameMap> gameMaps)
	        throws IOException
	{
		if (zipInputStream == null) {
			throw new IllegalArgumentException(
			        "zipInputStream == null");
		}
		if (zipEntry == null) {
			throw new IllegalArgumentException("zipEntry == null");
		}
		if (gameMaps == null) {
			throw new IllegalArgumentException("gameMaps == null");
		}

		String basename = getBasename(zipEntry);

		// get map
		IGameMap gameMap = gameMaps.get(basename);
		if (gameMap == null) {
			gameMap = new GameMap(basename);
			gameMaps.put(basename, gameMap);

			LOG.debug("Adding gameMap: " + basename);
		}

		// determine which component to load
		String extension = getExtension(zipEntry);

		if ("png".equals(extension)) {
			gameMap.setImage(GameMapIO.loadImage(zipInputStream));
			LOG.debug("Setting image: " + basename);
		} else if ("geo".equals(extension)) {
			gameMap.setGeometry(GameMapIO
			        .loadGeometry(zipInputStream));
			LOG.debug("Setting geometry: " + basename);
		} else if ("loc".equals(extension)) {
			gameMap.setLocations(GameMapIO
			        .loadLocations(zipInputStream));
			LOG.debug("Setting locations: " + basename);
		} else {
			LOG.warn("Ignoring unknown extension: " + extension);
		}
	}

	/**
	 * Get object basename.
	 * 
	 * @param zipEntry ZipEntry
	 * @return Basename
	 */
	protected static String getBasename(ZipEntry zipEntry)
	{
		if (zipEntry == null) {
			throw new IllegalArgumentException("zipEntry == null");
		}

		return zipEntry.getName().replaceAll(".*?[/\\\\](.*?)\\..*$",
		        "$1");
	}

	/**
	 * Get object extension.
	 * 
	 * @param zipEntry ZipEntry
	 * @return Basename
	 */
	protected static String getExtension(ZipEntry zipEntry)
	{
		if (zipEntry == null) {
			throw new IllegalArgumentException("zipEntry == null");
		}

		return zipEntry.getName().replaceAll(".*?[/\\\\].*?\\.(.*)$",
		        "$1");
	}
}
